Add --message-format flag.
authorAleksey Kladov <aleksey.kladov@gmail.com>
Thu, 11 Aug 2016 21:47:49 +0000 (00:47 +0300)
committerAleksey Kladov <aleksey.kladov@gmail.com>
Sun, 2 Oct 2016 14:28:26 +0000 (17:28 +0300)
14 files changed:
src/bin/bench.rs
src/bin/build.rs
src/bin/doc.rs
src/bin/install.rs
src/bin/run.rs
src/bin/rustc.rs
src/bin/rustdoc.rs
src/bin/test.rs
src/cargo/ops/cargo_compile.rs
src/cargo/ops/cargo_package.rs
src/cargo/ops/cargo_rustc/mod.rs
src/cargo/ops/mod.rs
tests/build.rs
tests/cargotest/support/mod.rs

index f2d028d34e46adeb5eb5526458faa21a00a6a69d..521c87c2f703eb218ff7d9f524aa9caa27e40f18 100644 (file)
@@ -16,6 +16,7 @@ pub struct Options {
     flag_verbose: u32,
     flag_quiet: Option<bool>,
     flag_color: Option<String>,
+    flag_message_format: Option<String>,
     flag_lib: bool,
     flag_bin: Vec<String>,
     flag_example: Vec<String>,
@@ -50,6 +51,7 @@ Options:
     -v, --verbose ...            Use verbose output
     -q, --quiet                  No output printed to stdout
     --color WHEN                 Coloring: auto, always, never
+    --message-format FMT         Error format: human, json-v1
     --frozen                     Require Cargo.lock and cache are up to date
     --locked                     Require Cargo.lock is up to date
 
@@ -75,7 +77,9 @@ pub fn execute(options: Options, config: &Config) -> CliResult<Option<()>> {
                           &options.flag_color,
                           options.flag_frozen,
                           options.flag_locked));
-
+    let message_format = try!(ops::MessageFormat::from_option(
+        &options.flag_message_format
+    ));
     let ops = ops::TestOptions {
         no_run: options.flag_no_run,
         no_fail_fast: false,
@@ -96,6 +100,7 @@ pub fn execute(options: Options, config: &Config) -> CliResult<Option<()>> {
                                             &options.flag_test,
                                             &options.flag_example,
                                             &options.flag_bench),
+            message_format: message_format,
             target_rustdoc_args: None,
             target_rustc_args: None,
         },
index 2f23ce1be3bf99ec86d3cf6f23064590426700a0..c4619f541e9e22545207a2da4719c6d9da978d60 100644 (file)
@@ -18,6 +18,7 @@ pub struct Options {
     flag_verbose: u32,
     flag_quiet: Option<bool>,
     flag_color: Option<String>,
+    flag_message_format: Option<String>,
     flag_release: bool,
     flag_lib: bool,
     flag_bin: Vec<String>,
@@ -52,6 +53,7 @@ Options:
     -v, --verbose ...            Use verbose output
     -q, --quiet                  No output printed to stdout
     --color WHEN                 Coloring: auto, always, never
+    --message-format FMT         Error format: human, json-v1
     --frozen                     Require Cargo.lock and cache are up to date
     --locked                     Require Cargo.lock is up to date
 
@@ -73,6 +75,9 @@ pub fn execute(options: Options, config: &Config) -> CliResult<Option<()>> {
                           &options.flag_color,
                           options.flag_frozen,
                           options.flag_locked));
+    let message_format = try!(ops::MessageFormat::from_option(
+        &options.flag_message_format
+    ));
 
     let root = try!(find_root_manifest_for_wd(options.flag_manifest_path, config.cwd()));
 
@@ -92,6 +97,7 @@ pub fn execute(options: Options, config: &Config) -> CliResult<Option<()>> {
                                         &options.flag_test,
                                         &options.flag_example,
                                         &options.flag_bench),
+        message_format: message_format,
         target_rustdoc_args: None,
         target_rustc_args: None,
     };
index c4139987cf753ca90df1f6b229e0911d28fdd911..83431ef66ff384f0c69f9405a937ab911fed8e71 100644 (file)
@@ -17,6 +17,7 @@ pub struct Options {
     flag_verbose: u32,
     flag_quiet: Option<bool>,
     flag_color: Option<String>,
+    flag_message_format: Option<String>,
     flag_package: Vec<String>,
     flag_lib: bool,
     flag_bin: Vec<String>,
@@ -47,6 +48,7 @@ Options:
     -v, --verbose ...            Use verbose output
     -q, --quiet                  No output printed to stdout
     --color WHEN                 Coloring: auto, always, never
+    --message-format FMT         Error format: human, json-v1
     --frozen                     Require Cargo.lock and cache are up to date
     --locked                     Require Cargo.lock is up to date
 
@@ -65,6 +67,9 @@ pub fn execute(options: Options, config: &Config) -> CliResult<Option<()>> {
                           &options.flag_color,
                           options.flag_frozen,
                           options.flag_locked));
+    let message_format = try!(ops::MessageFormat::from_option(
+        &options.flag_message_format
+    ));
 
     let root = try!(find_root_manifest_for_wd(options.flag_manifest_path, config.cwd()));
 
@@ -85,6 +90,7 @@ pub fn execute(options: Options, config: &Config) -> CliResult<Option<()>> {
                                             &empty,
                                             &empty,
                                             &empty),
+            message_format: message_format,
             release: options.flag_release,
             mode: ops::CompileMode::Doc {
                 deps: !options.flag_no_deps,
index 544b115e9a22d483d4b98f08837a484b470b40a1..b5d67fd411f818314726460fb215b5a6521a3ef2 100644 (file)
@@ -114,6 +114,7 @@ pub fn execute(options: Options, config: &Config) -> CliResult<Option<()>> {
         release: !options.flag_debug,
         filter: ops::CompileFilter::new(false, &options.flag_bin, &[],
                                         &options.flag_example, &[]),
+        message_format: ops::MessageFormat::Human,
         target_rustc_args: None,
         target_rustdoc_args: None,
     };
index 5f9843c83ba601f7a0ad17b6f11c800859009a84..4b03b53bc53f48a4ca097a34b492a7690151517a 100644 (file)
@@ -16,6 +16,7 @@ pub struct Options {
     flag_verbose: u32,
     flag_quiet: Option<bool>,
     flag_color: Option<String>,
+    flag_message_format: Option<String>,
     flag_release: bool,
     flag_frozen: bool,
     flag_locked: bool,
@@ -42,6 +43,7 @@ Options:
     -v, --verbose ...       Use verbose output
     -q, --quiet             No output printed to stdout
     --color WHEN            Coloring: auto, always, never
+    --message-format FMT    Error format: human, json-v1
     --frozen                Require Cargo.lock and cache are up to date
     --locked                Require Cargo.lock is up to date
 
@@ -61,6 +63,9 @@ pub fn execute(options: Options, config: &Config) -> CliResult<Option<()>> {
                           &options.flag_color,
                           options.flag_frozen,
                           options.flag_locked));
+    let message_format = try!(ops::MessageFormat::from_option(
+        &options.flag_message_format
+    ));
 
     let root = try!(find_root_manifest_for_wd(options.flag_manifest_path, config.cwd()));
 
@@ -91,6 +96,7 @@ pub fn execute(options: Options, config: &Config) -> CliResult<Option<()>> {
                 bins: &bins, examples: &examples,
             }
         },
+        message_format: message_format,
         target_rustdoc_args: None,
         target_rustc_args: None,
     };
index e81c8d18f7a6ad0bf0b3da636e8c709f975d0167..2743c3669f03fcb75f72b47eff494b45f57f59fb 100644 (file)
@@ -19,6 +19,7 @@ pub struct Options {
     flag_verbose: u32,
     flag_quiet: Option<bool>,
     flag_color: Option<String>,
+    flag_message_format: Option<String>,
     flag_release: bool,
     flag_lib: bool,
     flag_bin: Vec<String>,
@@ -55,6 +56,7 @@ Options:
     -v, --verbose ...        Use verbose output
     -q, --quiet              No output printed to stdout
     --color WHEN             Coloring: auto, always, never
+    --message-format FMT     Error format: human, json-v1
     --frozen                 Require Cargo.lock and cache are up to date
     --locked                 Require Cargo.lock is up to date
 
@@ -80,6 +82,9 @@ pub fn execute(options: Options, config: &Config) -> CliResult<Option<()>> {
                           &options.flag_color,
                           options.flag_frozen,
                           options.flag_locked));
+    let message_format = try!(ops::MessageFormat::from_option(
+        &options.flag_message_format
+    ));
 
     let root = try!(find_root_manifest_for_wd(options.flag_manifest_path,
                                               config.cwd()));
@@ -110,6 +115,7 @@ pub fn execute(options: Options, config: &Config) -> CliResult<Option<()>> {
                                         &options.flag_test,
                                         &options.flag_example,
                                         &options.flag_bench),
+        message_format: message_format,
         target_rustdoc_args: None,
         target_rustc_args: options.arg_opts.as_ref().map(|a| &a[..]),
     };
index 15e4a91ca667d32795a402cb46cefc560611ad87..79e368090b644241ffa7952c4e2981e09f392661 100644 (file)
@@ -17,6 +17,7 @@ pub struct Options {
     flag_release: bool,
     flag_quiet: Option<bool>,
     flag_color: Option<String>,
+    flag_message_format: Option<String>,
     flag_package: Option<String>,
     flag_lib: bool,
     flag_bin: Vec<String>,
@@ -52,6 +53,7 @@ Options:
     -v, --verbose ...        Use verbose output
     -q, --quiet              No output printed to stdout
     --color WHEN             Coloring: auto, always, never
+    --message-format FMT     Error format: human, json-v1
     --frozen                 Require Cargo.lock and cache are up to date
     --locked                 Require Cargo.lock is up to date
 
@@ -74,6 +76,9 @@ pub fn execute(options: Options, config: &Config) -> CliResult<Option<()>> {
                           &options.flag_color,
                           options.flag_frozen,
                           options.flag_locked));
+    let message_format = try!(ops::MessageFormat::from_option(
+        &options.flag_message_format
+    ));
 
     let root = try!(find_root_manifest_for_wd(options.flag_manifest_path,
                                               config.cwd()));
@@ -95,6 +100,7 @@ pub fn execute(options: Options, config: &Config) -> CliResult<Option<()>> {
                                             &options.flag_test,
                                             &options.flag_example,
                                             &options.flag_bench),
+            message_format: message_format,
             mode: ops::CompileMode::Doc { deps: false },
             target_rustdoc_args: Some(&options.arg_opts),
             target_rustc_args: None,
index c5e687a648388fb5e390a2a45b3a9f1037fef5f2..a2f1f5f129b017fbfdf0ffde661a558fbb2f5cb7 100644 (file)
@@ -23,6 +23,7 @@ pub struct Options {
     flag_verbose: u32,
     flag_quiet: Option<bool>,
     flag_color: Option<String>,
+    flag_message_format: Option<String>,
     flag_release: bool,
     flag_no_fail_fast: bool,
     flag_frozen: bool,
@@ -55,6 +56,7 @@ Options:
     -v, --verbose ...            Use verbose output
     -q, --quiet                  No output printed to stdout
     --color WHEN                 Coloring: auto, always, never
+    --message-format FMT         Error format: human, json-v1
     --no-fail-fast               Run all tests regardless of failure
     --frozen                     Require Cargo.lock and cache are up to date
     --locked                     Require Cargo.lock is up to date
@@ -92,6 +94,10 @@ pub fn execute(options: Options, config: &Config) -> CliResult<Option<()>> {
                           &options.flag_color,
                           options.flag_frozen,
                           options.flag_locked));
+    let message_format = try!(ops::MessageFormat::from_option(
+        &options.flag_message_format
+    ));
+
     let root = try!(find_root_manifest_for_wd(options.flag_manifest_path, config.cwd()));
 
     let empty = Vec::new();
@@ -124,6 +130,7 @@ pub fn execute(options: Options, config: &Config) -> CliResult<Option<()>> {
             release: options.flag_release,
             mode: mode,
             filter: filter,
+            message_format: message_format,
             target_rustdoc_args: None,
             target_rustc_args: None,
         },
index 512117ed8ba7ec44387e9d4df4b54bbd91b64a26..3b88365d5f953d7d10428dd8558ec58e2344c4cd 100644 (file)
@@ -59,6 +59,8 @@ pub struct CompileOptions<'a> {
     pub release: bool,
     /// Mode for this compile.
     pub mode: CompileMode,
+    /// `--error_format` flag for the compiler.
+    pub message_format: MessageFormat,
     /// Extra arguments to be passed to rustdoc (for main crate and dependencies)
     pub target_rustdoc_args: Option<&'a [String]>,
     /// The specified target will be compiled with all the available arguments,
@@ -74,6 +76,23 @@ pub enum CompileMode {
     Doc { deps: bool },
 }
 
+#[derive(Clone, Copy, PartialEq, Eq)]
+pub enum MessageFormat {
+    Human,
+    Json
+}
+
+impl MessageFormat {
+    pub fn from_option(opt: &Option<String>) -> CargoResult<MessageFormat> {
+        match opt.as_ref().map(|s| s.as_ref()) {
+            None | Some("human") => Ok(MessageFormat::Human),
+            Some("json-v1") => Ok(MessageFormat::Json),
+            Some(other) => bail!("argument for --message-format must be human or json-v1, \
+                                 but found `{}`", other)
+        }
+    }
+}
+
 pub enum CompileFilter<'a> {
     Everything,
     Only {
@@ -150,7 +169,7 @@ pub fn compile_ws<'a>(ws: &Workspace<'a>,
     let root_package = try!(ws.current());
     let CompileOptions { config, jobs, target, spec, features,
                          all_features, no_default_features,
-                         release, mode,
+                         release, mode, message_format,
                          ref filter, ref exec_engine,
                          ref target_rustdoc_args,
                          ref target_rustc_args } = *options;
@@ -242,6 +261,7 @@ pub fn compile_ws<'a>(ws: &Workspace<'a>,
         build_config.exec_engine = exec_engine.clone();
         build_config.release = release;
         build_config.test = mode == CompileMode::Test;
+        build_config.json_errors = message_format == MessageFormat::Json;
         if let CompileMode::Doc { deps } = mode {
             build_config.doc_all = deps;
         }
index dc12e46ffe8b8c992447808063d07dca63f3be11..2001c6c8711971ae41f969d8e6323eba3729dae3 100644 (file)
@@ -295,6 +295,7 @@ fn run_verify(ws: &Workspace, tar: &File, opts: &PackageOpts) -> CargoResult<()>
         filter: ops::CompileFilter::Everything,
         exec_engine: None,
         release: false,
+        message_format: ops::MessageFormat::Human,
         mode: ops::CompileMode::Build,
         target_rustdoc_args: None,
         target_rustc_args: None,
index 59d1ec57f632267b47826bb3ead91f83a32815b7..dac09a7ea38ef7a4f58db573efa9d7ee7ecbfd01 100644 (file)
@@ -5,6 +5,8 @@ use std::fs;
 use std::path::{self, PathBuf};
 use std::sync::Arc;
 
+use rustc_serialize::json;
+
 use core::{Package, PackageId, PackageSet, Target, Resolve};
 use core::{Profile, Profiles, Workspace};
 use core::shell::ColorConfig;
@@ -44,6 +46,7 @@ pub struct BuildConfig {
     pub release: bool,
     pub test: bool,
     pub doc_all: bool,
+    pub json_errors: bool,
 }
 
 #[derive(Clone, Default)]
@@ -212,7 +215,6 @@ fn rustc(cx: &mut Context, unit: &Unit) -> CargoResult<Work> {
         }
     }
     let has_custom_args = unit.profile.rustc_args.is_some();
-    let exec_engine = cx.exec_engine.clone();
 
     let filenames = try!(cx.target_filenames(unit));
     let root = cx.out_dir(unit);
@@ -240,7 +242,9 @@ fn rustc(cx: &mut Context, unit: &Unit) -> CargoResult<Work> {
     let cwd = cx.config.cwd().to_path_buf();
 
     rustc.args(&try!(cx.rustflags_args(unit)));
-
+    let json_errors = cx.build_config.json_errors;
+    let package_id = unit.pkg.package_id().clone();
+    let target = unit.target.clone();
     return Ok(Work::new(move |state| {
         // Only at runtime have we discovered what the extra -L and -l
         // arguments are for native libraries, so we process those here. We
@@ -266,7 +270,36 @@ fn rustc(cx: &mut Context, unit: &Unit) -> CargoResult<Work> {
         }
 
         state.running(&rustc);
-        try!(exec_engine.exec(rustc).chain_error(|| {
+        let process_builder = rustc.into_process_builder();
+        try!(if json_errors {
+            #[derive(RustcEncodable)]
+            struct Message<'a> {
+                reason: &'a str,
+                package_id: &'a PackageId,
+                target: &'a Target,
+                message: json::Json,
+            }
+            process_builder.exec_with_streaming(
+                &mut |line| assert!(line.is_empty()),
+                &mut |line| {
+                    let rustc_message = json::Json::from_str(line).unwrap_or_else(|_| {
+                        panic!("Compiler produced invalid json: `{}`", line)
+                    });
+
+                    let message = Message {
+                        reason: "rustc-message",
+                        package_id: &package_id,
+                        target: &target,
+                        message: rustc_message,
+                    };
+                    let encoded = json::encode(&message).unwrap();
+                    println!("{}", encoded);
+
+                },
+            ).map(|_| ())
+        } else {
+            process_builder.exec()
+        }.chain_error(|| {
             human(format!("Could not compile `{}`.", name))
         }));
 
@@ -495,6 +528,10 @@ fn build_base_args(cx: &Context,
         cmd.arg("--color").arg(&color_config.to_string());
     }
 
+    if cx.build_config.json_errors {
+        cmd.arg("--error-format").arg("json");
+    }
+
     cmd.arg("--crate-name").arg(&unit.target.crate_name());
 
     if !test {
index 48edabd94623b99e3549c3850ed42c89b50209b1..442d8c5a8aecab36779a03a2e9b9fba34b7aecdf 100644 (file)
@@ -1,6 +1,6 @@
 pub use self::cargo_clean::{clean, CleanOptions};
 pub use self::cargo_compile::{compile, compile_ws, resolve_dependencies, CompileOptions};
-pub use self::cargo_compile::{CompileFilter, CompileMode};
+pub use self::cargo_compile::{CompileFilter, CompileMode, MessageFormat};
 pub use self::cargo_read_manifest::{read_manifest,read_package,read_packages};
 pub use self::cargo_rustc::{compile_targets, Compilation, Layout, Kind, Unit};
 pub use self::cargo_rustc::{Context, LayoutProxy};
index 5657396382cc37d5400e0c1dbdd849c09ae3ff09..14817b167cd662f50d251f634545647da6a2d6c6 100644 (file)
@@ -2276,6 +2276,81 @@ fn explicit_color_config_is_propagated_to_rustc() {
 "));
 }
 
+#[test]
+fn compiler_json_error_format() {
+    if !is_nightly() { return }
+
+    let p = project("foo")
+        .file("Cargo.toml", r#"
+            [project]
+
+            name = "foo"
+            version = "0.5.0"
+            authors = ["wycats@example.com"]
+
+            [dependencies.bar]
+            path = "bar"
+        "#)
+        .file("src/main.rs", "fn main() { let unused = 92; }")
+        .file("bar/Cargo.toml", r#"
+            [project]
+
+            name = "bar"
+            version = "0.5.0"
+            authors = ["wycats@example.com"]
+        "#)
+        .file("bar/src/lib.rs", r#"fn dead() {}"#);
+
+    assert_that(p.cargo_process("build").arg("-v")
+                    .arg("--message-format").arg("json-v1"),
+                execs().with_json(r#"
+    {
+        "reason":"rustc-message",
+        "package_id":"bar 0.5.0 ([..])",
+        "target":{"kind":["lib"],"name":"bar","src_path":"[..]lib.rs"},
+        "message":{
+            "children":[],"code":null,"level":"warning","rendered":null,
+            "message":"function is never used: `dead`, #[warn(dead_code)] on by default",
+            "spans":[{
+                "byte_end":12,"byte_start":0,"column_end":13,"column_start":1,"expansion":null,
+                "file_name":"[..]","is_primary":true,"label":null,"line_end":1,"line_start":1,
+                "suggested_replacement":null,
+                "text":[{"highlight_end":13,"highlight_start":1,"text":"fn dead() {}"}]
+            }]
+        }
+    }
+
+    {
+        "reason":"rustc-message",
+        "package_id":"foo 0.5.0 ([..])",
+        "target":{"kind":["bin"],"name":"foo","src_path":"[..]main.rs"},
+        "message":{
+            "children":[],"code":null,"level":"warning","rendered":null,
+            "message":"unused variable: `unused`, #[warn(unused_variables)] on by default",
+            "spans":[{
+                "byte_end":22,"byte_start":16,"column_end":23,"column_start":17,"expansion":null,
+                "file_name":"[..]","is_primary":true,"label":null,"line_end":1,"line_start":1,
+                "suggested_replacement":null,
+                "text":[{"highlight_end":23,"highlight_start":17,"text":"[..]"}]
+            }]
+        }
+    }
+"#));
+}
+
+#[test]
+fn wrong_message_format_option() {
+    let p = project("foo")
+        .file("Cargo.toml", &basic_bin_manifest("foo"))
+        .file("src/main.rs", "fn main() {}");
+    p.build();
+
+    assert_that(p.cargo_process("build").arg("--message-format").arg("XML"),
+                execs().with_status(101)
+                       .with_stderr_contains("\
+[ERROR] argument for --message-format must be human or json-v1, but found `XML`"));
+}
+
 #[test]
 fn no_warn_about_package_metadata() {
     let p = project("foo")
index fb6c7daf6296108d138ad399236b413cb5126031..6de6df19764cf5366ea82995c24b6ae8714b9557 100644 (file)
@@ -258,7 +258,7 @@ pub struct Execs {
     expect_exit_code: Option<i32>,
     expect_stdout_contains: Vec<String>,
     expect_stderr_contains: Vec<String>,
-    expect_json: Option<Json>,
+    expect_json: Option<Vec<Json>>,
 }
 
 impl Execs {
@@ -288,7 +288,9 @@ impl Execs {
     }
 
     pub fn with_json(mut self, expected: &str) -> Execs {
-        self.expect_json = Some(Json::from_str(expected).unwrap());
+        self.expect_json = Some(expected.split("\n\n").map(|obj| {
+            Json::from_str(obj).unwrap()
+        }).collect());
         self
     }
 
@@ -324,8 +326,18 @@ impl Execs {
                                 &actual.stdout, true));
         }
 
-        if let Some(ref expect_json) = self.expect_json {
-            try!(self.match_json(expect_json, &actual.stdout));
+        if let Some(ref objects) = self.expect_json {
+            let lines = match str::from_utf8(&actual.stdout) {
+                Err(..) => return Err("stdout was not utf8 encoded".to_owned()),
+                Ok(stdout) => stdout.lines().collect::<Vec<_>>(),
+            };
+            if lines.len() != objects.len() {
+                return Err(format!("expected {} json lines, got {}",
+                                   objects.len(), lines.len()));
+            }
+            for (obj, line) in objects.iter().zip(lines) {
+                try!(self.match_json(obj, line));
+            }
         }
         Ok(())
     }
@@ -375,14 +387,9 @@ impl Execs {
 
     }
 
-    fn match_json(&self, expected: &Json, stdout: &[u8]) -> ham::MatchResult {
-        let stdout = match str::from_utf8(stdout) {
-            Err(..) => return Err("stdout was not utf8 encoded".to_owned()),
-            Ok(stdout) => stdout,
-        };
-
-        let actual = match Json::from_str(stdout) {
-             Err(..) => return Err(format!("Invalid json {}", stdout)),
+    fn match_json(&self, expected: &Json, line: &str) -> ham::MatchResult {
+        let actual = match Json::from_str(line) {
+             Err(e) => return Err(format!("invalid json, {}:\n`{}`", e, line)),
              Ok(actual) => actual,
         };